Skip to content

C#: Remove expanded assignments.#21452

Draft
michaelnebel wants to merge 15 commits intogithub:mainfrom
michaelnebel:csharp/expandedassignment
Draft

C#: Remove expanded assignments.#21452
michaelnebel wants to merge 15 commits intogithub:mainfrom
michaelnebel:csharp/expandedassignment

Conversation

@michaelnebel
Copy link
Contributor

@michaelnebel michaelnebel commented Mar 11, 2026

This PR has two goals, primarily to avoid making multiple upgrade/downgrade scripts:

  • Remove expanded assignments. This aligns our implementation with Java and should make adoption of the shared CFG library easier. It also simplifies expression handling and avoids caching a large predicate. Previously, when the extractor encountered a += b, it would synthesize a = a + b.
  • Swap the left- and right-hand-side child indices for assignments. This lets us remove several hacks/rotations currently used in the code.

Review on a commit-by-commit basis is encouraged.

Implementation notes

  • The implementation now “correctly” classifies a += b (e.g., when a and b are int) as an operator call, since it implicitly invokes the user-defined static operator on Int32.
  • The only compound assignment that does not call an operator is ??= as the null-coalescing operator is a built-in short-circuit like operation.
  • Added a new set of classes to represent expressions involving a specific operation (e.g., AddOperation, which can represent either a + b or a += b).

DCA notes

  • Performance appears unchanged.
  • The missing result for cs/unsynchronized-getter is due to removal of a false positive: the query did not properly account for expanded assignments.
  • The missing result for cs/web/xss looks acceptable: one alert is removed, but it is essentially a shorter path to the same underlying alert (which is here)
  • The new results for cs/useless-upcast look acceptable.
  • Remaining differences appear consistent with normal DCA background noise.

@github-actions github-actions bot added the C# label Mar 11, 2026
@michaelnebel michaelnebel force-pushed the csharp/expandedassignment branch from c94de85 to 1d8d712 Compare March 11, 2026 12:47
@michaelnebel michaelnebel force-pushed the csharp/expandedassignment branch 9 times, most recently from abb75be to 5021ea9 Compare March 16, 2026 10:00
@michaelnebel michaelnebel changed the title C#: Cleanup expanded assignments. C#: Remove expanded assignments. Mar 16, 2026
@michaelnebel michaelnebel force-pushed the csharp/expandedassignment branch 13 times, most recently from 41fef59 to 07144c2 Compare March 20, 2026 12:09
@michaelnebel
Copy link
Contributor Author

@hvitved : I would really appreciate a review of this PR even though it is still in draft mode. The PR still needs upgrade/downgrade scripts and more DCA runs.

@michaelnebel michaelnebel requested a review from hvitved March 20, 2026 13:18
@michaelnebel michaelnebel force-pushed the csharp/expandedassignment branch from 07144c2 to d2188dd Compare March 23, 2026 09:09
* An assignment operation. Either an arithmetic assignment operation
* (`AssignArithmeticOperation`), a bitwise assignment operation
* (`AssignBitwiseOperation`), or an event assignment (`AddOrRemoveEventExpr`).
* (`AssignArithmeticOperation`), a bitwise assignment operation or
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spurious "or"

Suggested change
* (`AssignArithmeticOperation`), a bitwise assignment operation or
* (`AssignArithmeticOperation`), a bitwise assignment operation

}

/**
* An assignment operation that corresponds to an operator call, for example `x += y` corresponds to `x = x + y`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reads somewhat off to me. Did you mean something like this instead?

Suggested change
* An assignment operation that corresponds to an operator call, for example `x += y` corresponds to `x = x + y`.
* An assignment operation that corresponds to an operator call, for example `x += y` corresponds to a call to `+=`.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some further elaboration on this piece of qldoc might also be nice to clarify exactly which compound assignments are considered OperatorCalls.

/**
* An assignment operation that corresponds to an operator call, for example `x += y` corresponds to `x = x + y`.
*/
class AssignCallOperation extends AssignOperation, OperatorCall, @assign_op_call_expr {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that most AssignOperations extend OperatorCall, then the qldoc for OperatorCall needs a tweak as it currently claims to only cover calls to user-defined operators.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we should consider changing the wording as I am not sure what a non user-defined operator is (unless it is a real built-in like &&). The operator used in a += b (when a and b are of type int) is a user-defined operator and it is declared here.

* An assignment operation that corresponds to an operator call, for example `x += y` corresponds to `x = x + y`.
*/
class AssignCallOperation extends AssignOperation, OperatorCall, @assign_op_call_expr {
override string toString() { result = "... " + this.getOperator() + " ..." }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This toString can be simplified.

Suggested change
override string toString() { result = "... " + this.getOperator() + " ..." }
override string toString() { result = AssignOperation.super.toString() }

import Expr

/** A binary operation that involves a null-coalescing operation. */
abstract private class NullCoalescingOperationImpl extends BinaryOperation { }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe all the classes in this file break some new ground wrt the AST class hierarchy design, so perhaps ought to be considered in a slightly broader bike-shedding session. Or is there something similar in other languages that I'm not aware of?
Regardless, I'm not a big fan of all the Add-prefixed names. Sure they're private, but they're still not very nice and they overload the meaning of "Add" in a context that already references "Add". Also, it's unfortunate that e.g. AddExpr and AssignAddExpr are not subclasses of AddOperation - that would be more natural. And if the latter is fixed, then I don't think we need all those addition-to-abstract-class classes that have such awkward names, which would address the former point as well.

Copy link
Contributor Author

@michaelnebel michaelnebel Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other option that I considered was extending the DB-scheme with @add_operation = @add_expr | @assign_add_expr; and similar for the other operations, then the QL part will look more "natural". Would you prefer such a solution instead?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

* ```
* Use `expr_parent` instead.
*/
cached
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May as well get rid of cached.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't, it's a public predicate in a cached module - so it has to be cached as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just remove it; the QL doc on the file says INTERNAL: Do not use..

@aschackmull
Copy link
Contributor

Besides the nits, code changes generally LGTM.

Copy link
Contributor

@hvitved hvitved left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM; no further comments than what @aschackmull has already pointed out, thanks for making this change.

@michaelnebel michaelnebel force-pushed the csharp/expandedassignment branch 2 times, most recently from fd781ef to 8f725cd Compare March 24, 2026 08:57
// Swap children for assignments, local variable declarations and add/remove event.
if isAssignment(parent)
then
child = 0 and expr_parent(e, 1, parent)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two need new_expressions(parent, _, _) and new_expression(e, _, _) to prevent db inconsistencies, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This query predicate is only concerned with contracting and rotating the parent/child relation.
The first case handles rotating the children of "assignments" (non-expanding), where we re-use existing expression entities (and the expressions are included in the update to the expressions table, which is the result of running new_expressions - however, I haven't tried running the code yet).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's exactly the contraction that I'm thinking about - i.e. currently the child-parent tuples for the expressions that we delete in new_expressions are being kept, which I think will create an inconsistent db.

child = 1 and expr_parent(e, 0, op)
)
or
expr_parent(e, child, parent) and not exists(getOperatorCall(parent))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, I believe.

@aschackmull
Copy link
Contributor

What about downgrade script?

@michaelnebel
Copy link
Contributor Author

What about downgrade script?

Uh, sorry - I should have mentioned that the PR is now considered "draft" again. I haven't even tried running the upgrade script yet - and yes - a downgrade script it also needed - but I would like to test the upgrade script first.

@michaelnebel michaelnebel force-pushed the csharp/expandedassignment branch from 8f725cd to 028f2b8 Compare March 24, 2026 14:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants